local license =
[[----------------------------------------------------------------------
  **** BEGIN LICENSE BLOCK ****
	A simple flocking simulator written in LUA.

    Copyright (C) 2010
	Eric Fredericksen
	www.pttpsystems.com
	eric@pttpsystems.com

	This file is part of Flocker.

    Flocker is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

  **** END LICENSE BLOCK ****
------------------------------------------------------------------------]]


-- yes, redundant, but not bad form
assert (flocker, "Flocker Engine: Failed to find global variable flocker")

require("flockerutils")

--[[======================== SOME OPENGL HELPER INFRASTRUCTURE =========================]]
flocker.ogl = {}

-- just make one of these for general use; NOTE: this requires luagl 1.4 or higher
flocker.ogl.QUAD = glu.NewQuadric()flocker.ogl.red		= { 1, 0, 0, 1 }
flocker.ogl.red		= { 1, 0, 0, 1 }
flocker.ogl.green	= { 0, 1, 0, 1 }
flocker.ogl.blue	= { 0, 0, 1, 1 }
flocker.ogl.yellow	= { 1, 1, 0, 1 }
flocker.ogl.cyan	= { 0, 1, 1, 1 }
flocker.ogl.magenta	= { 1, 0, 1, 1 }

flocker.ogl.black	= { 0.00, 0.00, 0.00, 1 }
flocker.ogl.dkgray	= { 0.25, 0.25, 0.25, 1 }
flocker.ogl.gray	= { 0.50, 0.50, 0.50, 1 }
flocker.ogl.ltgray	= { 0.75, 0.75, 0.75, 1 }
flocker.ogl.white	= { 1.00, 1.00, 1.00, 1 }

flocker.ogl.worldBoxGap			= 20

-- experimental
flocker.ogl.enableFog			= false
flocker.ogl.fogIntensity		= 0.5		-- in [0.0, 1.0]


--[[================ camera and avatar manipulation =========================================]]


function flocker.ogl.AccumulatePitchAndYaw( self, dPitch, dYaw )
	self.pitch = self.pitch + dPitch
	self.yaw   = self.yaw   + dYaw
	self.pitch = math.min( 89, self.pitch)
	self.pitch = math.max(-89, self.pitch)
end

function flocker.ogl.SetPitchAndYaw(self, pitch, yaw )
	self.pitch, self.yaw = pitch, yaw
end

flocker.ogl.camera =
{	--[[Distance and rotation around the avatar in spherical coords
		The camera is always facing the avatar.
		]]
	  distance	= 100
	, pitch	= 0
	, yaw	= 0

	, fov = 60 -- initial FOV

	, SetPitchAndYaw 		= flocker.ogl.SetPitchAndYaw
	, AccumulatePitchAndYaw = flocker.ogl.AccumulatePitchAndYaw

	, GetVectorToAvatar 	= function(self)
			local vec = { 0, 0, self.distance }
			vec3.rotDegIt( vec, 0, self.pitch, self.yaw )
			return unpack(vec)
		end
}

flocker.ogl.avatar =
{	-- initial position
	  x		= flocker.toroidX/2
	, y		= flocker.toroidY/2
	, z		= -10 -- just outside the box

	-- movement direction and velocity (rho) in spherical coords
	, speed	= 0
	, pitch	= 0
	, yaw	= 0

	, SetPosition 		= function(self, x,y,z ) self.x, self.y, self.z = x,y,z end
	, SetSpeed 			= function(self, speed ) self.speed = speed end

	, SetPitchAndYaw	= flocker.ogl.SetPitchAndYaw
	, AccumulatePitchAndYaw = flocker.ogl.AccumulatePitchAndYaw

	, GetDirectionVector= function(self, distance)
			distance = distance or 1.0
			local vec = { 0, 0, distance }
			vec3.rotDegIt( vec, 0, self.pitch, self.yaw )
			return unpack(vec)
		end

	, UpdatePosition 	= function(self)
			local dx, dy, dz = self:GetDirectionVector( self.speed )
			self.x = self.x + dx
			self.y = self.y + dy
			self.z = self.z + dz
		end
}

--[[============================== LIGHTING STUFF =============================================]]

flocker.ogl.lighting =
{
	  enableLighting			= true
	, enableColorMaterial		= true
	, globalAmbient 			= {0.2, 0.2, 0.2, 1}
	, lmLocalViewer				= false
	, lmTwoSided				= false
--[[ luaGL does not expose this for some reason
	, lmSeparateSpecularColor	= false			]]

	, lights 					= {}
	, lightsByName 				= {}

}
--[[ for some strange reason, flocker.ogl.lighting will not accept a function
	called "SetGlobalAmbient" -- it just remains nil, silently failing.
	So, I switched to SetGAmbient.
	]]
function flocker.ogl.lighting.SetGAmbient(self, ambient)
	self.globalAmbient = CopyArray( ambient or {0.2, 0.2, 0.2, 1})
end

function flocker.ogl.lighting.ClearLights(self)
	self.lights = {}
	self.lightsByName = {}
end

function flocker.ogl.lighting.ConfigureOpenGL()

	myogl 		= flocker.ogl
	lighting	= flocker.ogl.lighting

	if lighting.enableColorMaterial then
		gl.Enable(  gl.COLOR_MATERIAL	)
	else
		gl.Disable( gl.COLOR_MATERIAL	)
	end

	if not lighting.enableLighting then

		gl.Disable( gl.LIGHTING			)

		-- light configuration is unnecessary

		return nil
	end

	gl.Enable(		gl.LIGHTING			)
	gl.ShadeModel( 	gl.SMOOTH			)

	gl.LightModel( gl.LIGHT_MODEL_AMBIENT, 			lighting.globalAmbient )
	gl.LightModel( gl.LIGHT_MODEL_LOCAL_VIEWER, 	lighting.lmLocalViewer	and gl.TRUE or  gl.FALSE )
	gl.LightModel( gl.LIGHT_MODEL_TWO_SIDE, 		lighting.lmTwoSided 	and gl.TRUE or  gl.FALSE )

	--[[ for some reason this control is not exposed int luaGL
	gl.LightModel( gl.LIGHT_MODEL_COLOR_CONTROL,
		lighting.separateSpecularColor and gl.SEPARATE_SPECULAR_COLOR or  gl.SINGLE_COLOR
		)
]]

	-- configure all the lights
	for id, light in ipairs(lighting.lights) do
		--print("configure light:", light.luaGLId, light.name)
		-- MUST use the colon syntax to pass the object
		light:ConfigureLightOpenGL()
	end

	gl.Finish()
end

function flocker.ogl.LuaGLLightIdFromIndex( k )
	-- use our knowldge of the light id naming in luagl
	return "LIGHT"..tostring(k)
end

function flocker.ogl.lighting.GetLight( name )
	return flocker.ogl.lighting.lightsByName[name]
end


--[[================== light object member functions ===================================]]
function flocker.ogl.lighting.SetAttenuation( light, c, l, q )
	light.attenuation = {c and 1, l and 0, q and 0}
end

function flocker.ogl.lighting.SetRelativePosition( light, position )
	light.position  = proto.position or { 0, 0, 1, 0 } -- OGL default
	light.position[4] = 1 -- as requested
end

function flocker.ogl.lighting.UpdateWorldPosition(light, wx, wy, wz )
	local pos = light.position

	light.worldPosition[1] = math.floor( light.position[1] * wx )
	light.worldPosition[2] = math.floor( light.position[2] * wy )
	light.worldPosition[3] = math.floor( light.position[3] * wz )
	light.worldPosition[4] = light.position[4]
end


function flocker.ogl.lighting.SetSpotDirection( light, theta, phi )

	light.spotTheta, light.spotPhi = theta, phi
	local sd = light.spotDirection
	sd[1], sd[2], sd[3] = SphericalToCartesian( 1, theta, phi, true  )
end

function flocker.ogl.lighting.ConfigureLightOpenGL( light )

	local luaGLId = light.luaGLId -- a string of the form "LIGHTn" where 'n' is in [0,7]

	if not light.enabled then
		gl.Disable( luaGLId )

	else
	local pos = light.worldPosition
		--print ( "Enabling "..luaGLId.." at world position", unpack(pos) )
		gl.Light( luaGLId,	gl.POSITION,	light.worldPosition)

		gl.Light( luaGLId,	gl.AMBIENT,		light.ambient	)
		gl.Light( luaGLId,	gl.DIFFUSE,		light.diffuse	)
		gl.Light( luaGLId,	gl.SPECULAR,	light.specular	)
		gl.Light( luaGLId,	gl.CONSTANT_ATTENUATION,	light.attenuation[1] )
		gl.Light( luaGLId,	gl.LINEAR_ATTENUATION,		light.attenuation[2] )
		gl.Light( luaGLId,	gl.QUADRATIC_ATTENUATION,	light.attenuation[3] )

		if light.spotEnabled then
			gl.Light( luaGLId,	gl.SPOT_CUTOFF,		light.spotCutoff	)
			gl.Light( luaGLId,	gl.SPOT_DIRECTION,	light.spotDirection	)
			gl.Light( luaGLId,	gl.SPOT_EXPONENT,	light.spotExponent	)
		else
			gl.Light( luaGLId,	gl.SPOT_CUTOFF,		180			)
			gl.Light( luaGLId,	gl.SPOT_DIRECTION,	{0,0,-1}	)
			gl.Light( luaGLId,	gl.SPOT_EXPONENT,	0.0			)
		end

		gl.Enable( luaGLId )
	end
end


-- call this without parameters to reconfigure defaults
function flocker.ogl.lighting.ConfigureSpotLight( light, enabled, direction, cutoff, exponent )

	light.spotEnabled		= enabled and true 	or false

	light.spotDirection 	= CopyArray( enabled and direction	or { 0, 0, -1 } )

	-- setting the cutoff to 180 is the disablement step but we take care of that in the configuration above
	light.spotCutoff 		= enabled and cutoff or 90

	light.spotExponent		= enabled and exponent	or 0.0
end

--[[================== light object constructor function ===================================]]
function flocker.ogl.lighting.CreateLightFromPrototype( proto )

	assert( proto, "flocker.ogl.CreateLightFromPrototype: missing light proto")

	local lighting = flocker.ogl.lighting

	--[[ We will DECORATE this object to duck-type it. We also
		encapsulate all the light functionality using OGL defaults
		(as listed in my reference)
		]]

	local light = proto

	-- add member functions
	light.SetAttenuation 		= flocker.ogl.lighting.SetAttenuation

	light.SetRelativePosition	= flocker.ogl.lighting.SetRelativePosition
	light.UpdateWorldPosition	= flocker.ogl.lighting.UpdateWorldPosition

	light.SetSpotDirection		= flocker.ogl.lighting.SetSpotDirection

	light.ConfigureSpotLight	= flocker.ogl.lighting.ConfigureSpotLight

	light.ConfigureLightOpenGL	= flocker.ogl.lighting.ConfigureLightOpenGL

	-- if we are replacing a named light, then get that index, else take the next available slot
	local index = proto.index or #lighting.lights

	-- must use insert for the index to progress properly
	table.insert( lighting.lights, light)
	lighting.lightsByName[proto.name]	= light

	light.index		= index
	light.luaGLId	= flocker.ogl.LuaGLLightIdFromIndex( index )
	light.name		= proto.name or light.luaGLId

	light.enabled	= proto.enabled and true or false

	light.ambient 	= CopyArray( proto.ambient  or  {0, 0, 0, 1} )

	-- white for light 0, black otherwise
	local default = (index==0) and {1, 1, 1, 1} or {0, 0, 0, 1}
	light.specular	= CopyArray( proto.specular or default )
	light.diffuse	= CopyArray( proto.diffuse  or default )

			--[[Position is determined by W in {x,y,z, W }, the FOURTH
				entry in the array. If it is non-zero then it is a positional light
				]]
	light.position = CopyArray( proto.position or { 0, 0, 1, 0 } )
	if not light.position[4] then light.position[4] = 0 end -- fix possible error

	light.worldPosition	= CopyArray( light.position )

			-- positional light configuration; start with OGL defaults
	light.attenuation 	= CopyArray( proto.attenuation or {1, 0, 0} ) -- att = 1/( [1] + d*[2] + d*[3]^2 )

	light:ConfigureSpotLight(proto.spotEnabled, proto.spotDirection, proto.spotCutoff, proto.spotExponent )

	local __
	__, light.spotTheta, light.spotPhi = CartesianToSpherical( light.spotDirection, nil, nil, true  )

	return light
end


function flocker.ogl.ConfigureMaterialOpenGL(self )

	local whichFaces = gl.FRONT_AND_BACK
	gl.Material( whichFaces, gl.DIFFUSE, 	self.diffuse 	)
	gl.Material( whichFaces, gl.AMBIENT, 	self.ambient 	)
	gl.Material( whichFaces, gl.SPECULAR,	self.specular	)
	gl.Material( whichFaces, gl.SHININESS,	self.shininess	)
	gl.Material( whichFaces, gl.EMISSION, 	self.emission	)
end

function flocker.ogl.CreateMaterialFromPrototype( prototype  )
	-- in case of empty call
	prototype = prototype or {}

	local material = { shininess = {} }
	-- OpenGL defaults as per the reference
	material.ambient		= prototype.ambient		or {0.2, 0.2, 0.2, 1}
	material.diffuse		= prototype.diffuse		or {0.8, 0.8, 0.8, 1}
	material.specular		= prototype.specular	or {0.0, 0.0, 0.0, 1}
	material.shininess[1]	= prototype.shininess	or 0.0
	material.emission		= prototype.emission	or {0.0, 0.0, 0.0, 1}

	material.ConfigureMaterialOpenGL = flocker.ogl.ConfigureMaterialOpenGL

	return material

end


function flocker.ogl.RenderVertices(vertices, beginArg, lineWidth, color )
	gl.LineWidth( lineWidth )
	gl.Color( color )
	gl.VertexPointer( vertices )
	gl.DrawArrays( beginArg, 0, #vertices )
end

flocker.ogl.maxCircleSteps = 180
flocker.ogl.minCircleSteps = 8
flocker.ogl.debug = false

function flocker.ogl.CreateCircle( radius, stepsAroundCircle )

	-- choose a rational angle resolution based on expected viewing, if one has not been requested
	stepsAroundCircle = stepsAroundCircle or 4*radius
	stepsAroundCircle = math.min(stepsAroundCircle, flocker.ogl.maxCircleSteps)
	stepsAroundCircle = math.max(stepsAroundCircle, flocker.ogl.minCircleSteps)

	local twoPi = 2*math.pi
	local circumference = twoPi*radius
	local angleDelta = twoPi/stepsAroundCircle

	local vertices = {}
	for k=1, stepsAroundCircle do
		local theta = -k*angleDelta -- draw the circle CCW for backface culling
		local vertex = { radius*math.cos(theta), radius*math.sin(theta) }

		table.insert( vertices, vertex )
	end

	vertices.RenderVertices = flocker.ogl.RenderVertices

	return vertices
end


-- set up our vertex cache -- may update this to use diplay lists (later)
flocker.ogl.vertexCache = {}

function flocker.ogl.ClearVertexCache()
	flocker.ogl.vertexCache = { circle={} }
end

function flocker.ogl.RetrieveVertices( itemType, itemId, resolution )

	local myogl = flocker.ogl

	local typeCache = myogl.vertexCache[ itemType ]
	flocker.assert( typeCache
		, "flocker.ogl.RetrieveVertices: unknown itemType"
		, function () print("type and id: ", itemType, itemId ) end
		)

	local vertices = typeCache[ radius ]

	if "circle" == itemType and not vertices then
		local radius = itemId
		vertices = myogl.CreateCircle( radius, resolution )
		myogl.vertexCache[ radius ] = vertices
	end

	return vertices
end

function flocker.ogl.DrawCircle( circle, stepsAroundCircle )

	-- choose a rational angle resolution based on expected viewing, if one has not been requested
	stepsAroundCircle = stepsAroundCircle or 4*circle.r
	stepsAroundCircle = math.min(stepsAroundCircle, flocker.ogl.maxCircleSteps)
	stepsAroundCircle = math.max(stepsAroundCircle, flocker.ogl.minCircleSteps)

	local radius = circle.r
	local twoPi = 2*math.pi
	local circumference = twoPi*radius
	local angleDelta = twoPi/stepsAroundCircle

	gl.LineWidth( circle.w )

	gl.Begin( gl.LINE_LOOP )
		gl.Color( circle.c )
		for theta = 0, twoPi, angleDelta do
			gl.Vertex( circle.x + radius*math.cos(theta), circle.y + radius*math.sin(theta) )
		end
	gl.End()

end

function flocker.ogl.DrawSquare( sx, sy, color, linewidth )

	local myogl = flocker.ogl
	linewidth = linewidth or 5
	color = color or myogl.green
	-- FRONT
	gl.Color( color  )
	gl.LineWidth( linewidth )


--	gl.Disable(gl.LINE_SMOOTH)
	local ofs = linewidth/2 -- edge offset
	gl.Begin( gl.LINE_LOOP )
		gl.Vertex(-ofs, 	-ofs, 	-ofs)
		gl.Vertex(-ofs, 	sy+ofs,	-ofs)
		gl.Vertex(sx+ofs, 	sy+ofs,	-ofs)
		gl.Vertex(sx+ofs, 	-ofs,	-ofs)
	gl.End()
--	gl.Enable(gl.LINE_SMOOTH)

end

-- a general purpose wall-geometry builder
function flocker.ogl.CreateQuadPlaneStrips( sx, sy, nx, ny )

	nX = math.floor(nx); nY = math.floor(ny)
	assert( sx and (sx>0) , "flocker.ogl.DrawQuadPlane: bad sx")
	assert( sy and (sy>0) , "flocker.ogl.DrawQuadPlane: bad sy")
	assert( nx and (nx>0) , "flocker.ogl.DrawQuadPlane: bad nx")
	assert( ny and (ny>0) , "flocker.ogl.DrawQuadPlane: bad ny")

	--[[	Create quadstrips from left to right, top to bottom.
			We'll have ny strips each with nx quadx
		]]

	local dx = sx/ny
	local dy = sy/ny

	local quadPlane = {}
	-- ny strips
	for y = 0, (ny-1) do

		local y0, y1 = y*dy, (y+1)*dy
		if y == ny then y0 = sy end -- adjust for roundoff

		local strip = {}

		-- nx + 1 slices along the strip
		for x = 0, nx do
			local x0 = x*dx
			if x == nx then x1 = sx end -- adjust for roundoff

			table.insert( strip, { x0, y0 } )
			table.insert( strip, { x0, y1 } )

		end

		table.insert( quadPlane, strip )

	end

	return quadPlane

end

-- a general purpose wall-geometry builder
function flocker.ogl.DrawQuadPlane( sx, sy, nx, ny, c )

	assert( c and "table" == type(c) , "flocker.ogl.DrawQuadPlane: bad color")

	local quadPlane = flocker.ogl.CreateQuadPlaneStrips( sx, sy, nx, ny )

	for __, quadStrip in pairs( quadPlane ) do
		flocker.ogl.RenderVertices(quadStrip, gl.QUAD_STRIP, 1, c )
	end

end


-- a general purpose wall-geometry builder
function flocker.ogl.DrawQuadPlaneOLD( sx, sy, nx, ny, c )

	nX = math.floor(nx); nY = math.floor(ny)
	assert( sx and (sx>0) , "flocker.ogl.DrawQuadPlane: bad sx")
	assert( sy and (sy>0) , "flocker.ogl.DrawQuadPlane: bad sy")
	assert( nx and (nx>0) , "flocker.ogl.DrawQuadPlane: bad nx")
	assert( ny and (ny>0) , "flocker.ogl.DrawQuadPlane: bad ny")
	assert( c and "table" == type(c) , "flocker.ogl.DrawQuadPlane: bad color")

	--[[	Draw quadstrips from left to right, top to bottom.
			We'll have ny strips each with nx quadx
		]]

	local dx = sx/ny
	local dy = sy/ny


	if c then gl.Color( c ) end

	-- ny strips
	for y = 0, (ny-1) do

		local y0, y1 = y*dy, (y+1)*dy
		if y == ny then y0 = sy end -- adjust for roundoff

		gl.Begin( gl.QUAD_STRIP )

			-- nx + 1 slices along the strip
			for x = 0, nx do
				local x0 = x*dx
				if x == nx then x1 = sx end -- adjust for roundoff

				gl.Vertex( x0, y0 )
				gl.Vertex( x0, y1 )

			end

		gl.End()

	end

end



function flocker.ogl.DrawWorldBox( solid, buffer )

	local myogl = flocker.ogl
	-- show the boundaries of the cube with polygons

	local boxType = solid and gl.POLYGON or gl.LINE_LOOP

	local sx, sy, sz = flocker.toroidX, flocker.toroidY, tonumber(flocker.toroidZ)

	local buffer = buffer or 0

	local squareColor = {0,1,0,0.75}

	local squareLineWidth = 2

	gl.PushMatrix()	-- BACK FACE
		gl.Translate( 0, 0, sz + buffer )
		myogl.DrawSquare(sx, sy, squareColor, squareLineWidth)
		flocker.ogl.DrawQuadPlane( sx, sy, 30, 30, myogl.gray )
	gl.PopMatrix()

	gl.PushMatrix()	-- FRONT FACE
		gl.Translate( sx, 0, -buffer )
		gl.Rotate( 180, 0, 1, 0 ) -- face it inwards
		myogl.DrawSquare(sx, sy, {1, 0, 0, 0.75}, squareLineWidth)
		flocker.ogl.DrawQuadPlane( sx, sy, 30, 30, myogl.gray )
	gl.PopMatrix()

	gl.PushMatrix() -- LEFT FACE (RIGHT face in frustrum)
		gl.Translate( -buffer, 0, 0 )
		gl.Rotate( -90, 0, 1, 0 )
		myogl.DrawSquare(sz, sy, squareColor, squareLineWidth)
		flocker.ogl.DrawQuadPlane( sz, sy, 30, 30, myogl.gray )
	gl.PopMatrix()

	gl.PushMatrix() -- RIGHT FACE (LEFT face in frustrum)
		-- the face vertex ordering makes the normals one-sided, so we need this translation/rotatation
		gl.Translate( sx + buffer, 0, sz )
		gl.Rotate( 90, 0, 1, 0 )
		myogl.DrawSquare(sz, sy, squareColor, squareLineWidth)
		flocker.ogl.DrawQuadPlane( sz, sy, 30, 30, myogl.gray )
	gl.PopMatrix()

	gl.PushMatrix() -- BOTTOM FACE
		gl.Translate( 0, -buffer, 0 )
		gl.Rotate( 90, 1, 0, 0 )
		myogl.DrawSquare(sx, sz, squareColor, squareLineWidth)
		flocker.ogl.DrawQuadPlane( sx, sz, 30, 30, myogl.gray )
	gl.PopMatrix()

	gl.PushMatrix() -- TOP FACE
		-- the face vertex ordering makes the normals one-sided, so we need this translation/rotatation
		gl.Translate( 0, sy + buffer, sz  )
		gl.Rotate( -90, 1, 0, 0 )
		myogl.DrawSquare(sx, sz, squareColor, squareLineWidth)
		flocker.ogl.DrawQuadPlane( sx, sz, 30, 30, myogl.gray )
	gl.PopMatrix()


end



function flocker.ogl.RefreshDisplay( self )

	-- syntax sweetener
	local myogl 	= flocker.ogl
	local lighting	= myogl.lighting
	local avatar 	= myogl.avatar
	local camera 	= myogl.camera

	-- swap to new buffer
	iup.GLMakeCurrent( self )

	-- clean up display
	local bgcolor = flocker.colorMap[ flocker.bgColor or 1 ]

	gl.ClearColor( bgcolor[1], bgcolor[2], bgcolor[3], bgcolor[4] )
	gl.Clear(gl.COLOR_BUFFER_BIT)
	gl.Clear(gl.DEPTH_BUFFER_BIT)

	-- for vertexpointer caching
	gl.EnableClientState(gl.VERTEX_ARRAY)

	-- enable smooth lines
	gl.Enable(gl.BLEND)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	gl.Enable(gl.LINE_SMOOTH)

	--[[ Turn on back-face culling -- this has no effect on frame rate under current conditions ]]
	gl.CullFace( gl.BACK )
	gl.Enable( gl.CULL_FACE )

	-- set the viewport
	gl.Viewport(0, 0, flocker.toroidX, flocker.toroidY)

	-- prepare to configure ortho/perspective
	-- DO SO BEFORE calls to glulookat
	gl.MatrixMode( gl.PROJECTION )
	gl.LoadIdentity()

	-- configure view based one 2D/3D
	if flocker.threeDeeFlocking then

		-- may want to come back and invert the z-axis usage all around
		glu.Perspective(camera.fov, flocker.toroidX / flocker.toroidY, 1, flocker.toroidZ*9 )

		local ax, ay, az = camera:GetVectorToAvatar()

		-- use the ModelView matrix for the rest of the rendering
		gl.MatrixMode( gl.MODELVIEW )
		gl.LoadIdentity()

		glu.LookAt(
				-- eye position relative to avatar
				avatar.x - ax,	avatar.y - ay,		avatar.z - az,
				-- we are always looking at the avatar
				avatar.x, 		avatar.y, 			avatar.z,
				-- we always have our head up
				0, 1, 0)

		gl.Enable(gl.DEPTH_TEST)
		gl.DepthFunc( gl.LEQUAL )

	else
		-- use world coordinates that match the cdCanvas ones
		gl.Ortho(flocker.toroidX, 0, 0, flocker.toroidY, -1, flocker.toroidZ)

		-- use the ModelView matrix for the rest of the rendering
		gl.MatrixMode( gl.MODELVIEW )
		gl.LoadIdentity()

		gl.Disable(gl.DEPTH_TEST)

	end

	-- always configure lighting
	lighting.ConfigureOpenGL()

	if flocker.threeDeeFlocking then

		-- use some shinier material than the default
		local worldBoxMaterial = flocker.ogl.CreateMaterialFromPrototype(
			{
				  ambient	= { 0.5, 0.5, 0.5, 1 }
				, diffuse	= { 0.5, 0.5, 0.5, 1 }
				, specular	= { 0.8, 0.8, 0.8, 1 }
				, shininess	= 10
				, emission	= { 0, 0, 0, 1 }
			}
		)

		-- so we can emphasize spotlights on world box
		worldBoxMaterial:ConfigureMaterialOpenGL()

		-- so that threeD stuff is overlapped correctly
		myogl.DrawWorldBox(true, flocker.ogl.worldBoxGap ) -- (solid, gap at edges)

		--[[ HMM - when I put this block after all the other rendering (where I
			can use transparency) it interferes with the lighting. I need to
			figure out why that is, but it is a result of calling
					myogl.QUAD:(Cylinder,etc.)
		]]
		-- display the avatar
		gl.Disable( gl.CULL_FACE ) -- we can move around it so no culling
		gl.PushMatrix()
			gl.Translate( avatar.x, avatar.y, avatar.z  )
			gl.Rotate( avatar.yaw,		0,1,0 )
			gl.Rotate( avatar.pitch,	1,0,0 )

			gl.Color( {0,0,0, 1} )
			myogl.QUAD:DrawStyle( glu.LINE )
			gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	{1, 1, 0, 1}	)
			myogl.QUAD:Cylinder( 3, 7, 10, 20, 3)

			local height = 5
			gl.Translate( 0 ,0, -height )

			gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	{0, 1, 0, 1}	)
			myogl.QUAD:Cylinder( 3, 3, height, 20, 2)

		gl.PopMatrix()

		gl.Enable( gl.CULL_FACE )


	end



	for __, light in pairs(lighting.lights) do
		-- show the lights

		gl.Color( light.diffuse )
		if light.enabled then

			gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	light.diffuse	)

			gl.PushMatrix()

				gl.Translate( light.worldPosition[1], light.worldPosition[2], light.worldPosition[3]  )

				if light.spotEnabled then
					gl.Rotate( light.spotPhi,	0,0,1 )
					gl.Rotate( light.spotTheta,	0,1,0 )

					myogl.QUAD:DrawStyle( glu.LINE )
					myogl.QUAD:Cylinder( 5, 30, 60, 10, 10)

					gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	myogl.black	)
					myogl.QUAD:DrawStyle( glu.FILL )
				--	myogl.QUAD:DrawStyle( glu.LINE )
					myogl.QUAD:Sphere(10, 10, 10 )

				else
				--	myogl.QUAD:DrawStyle( glu.FILL )
					myogl.QUAD:DrawStyle( glu.LINE )
					myogl.QUAD:Sphere(20, 15, 15 )

				end
			gl.PopMatrix()

		end

	end

	gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, myogl.black ) -- reset

	-- ask all the birds for their display needs
	local displayPrimitives = { lines={}, circles={}, birds={}, deaders={} }
	for __, thisBird in pairs( flocker.birds ) do
		thisBird:GetDisplayList( displayPrimitives )
	end

	--[[Vertex arrays are cached for the requested circle size, but just for this display frame ]]
	myogl.ClearVertexCache()

	-- render the bird avatar
	for __, bird in pairs( displayPrimitives.birds ) do

		gl.PushMatrix()

			gl.Material( gl.FRONT_AND_BACK, gl.SPECULAR,	bird.head.c	)
			gl.Material( gl.FRONT_AND_BACK, gl.SHININESS,	{20}	)
			gl.Material( gl.FRONT_AND_BACK, gl.AMBIENT,		{0.5,0.5,0.5, 1} )
			gl.Material( gl.FRONT_AND_BACK, gl.DIFFUSE,		bird.head.c	)

			-- set emission property to black
			gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	myogl.black	)

			local headpos = bird.head.p
			gl.Translate( headpos[1], headpos[2], headpos[3] )

			-- draw the body
			gl.Color( bird.body.c )
			gl.LineWidth( bird.body.w )
			gl.Begin( gl.LINES )
				gl.Vertex( {0,0,0} )
				gl.Vertex( bird.body.v )
			gl.End()

			-- draw the head
			gl.Color( bird.head.c )

			myogl.QUAD:Sphere( bird.head.r, 15, 15 )

			local x,y,z = unpack(bird.body.v)
			gl.Translate( x,y,z )

			-- draw the tail
			local pitch, yaw, length = OGLCartesianToPitchAndYawOGL( x, y, z, true )
			gl.Rotate( yaw,		0,1,0 )
			gl.Rotate( pitch,	1,0,0 )

			gl.Disable(gl.CULL_FACE )
		--	myogl.QUAD:Cylinder( 1, 1, length, 10, 2)
			myogl.QUAD:Sphere( 2, 10, 10 )
			gl.Enable( gl.CULL_FACE )

--[[
]]
		gl.PopMatrix()

		-- do we have any ancilliary halos to draw?
		if flocker.showRepel or flocker.showDetect or bird.aura then
			gl.PushMatrix()

				local birdpos = bird.p

				gl.Translate( birdpos[1], birdpos[2], birdpos[3]+1e-3 )

				if bird.aura then

					gl.Material( gl.FRONT_AND_BACK, gl.SPECULAR,	bird.aura.c	)
					gl.Material( gl.FRONT_AND_BACK, gl.SHININESS,	{20}	)
					gl.Material( gl.FRONT_AND_BACK, gl.AMBIENT,		{0.5,0.5,0.5, 1} )
					gl.Material( gl.FRONT_AND_BACK, gl.DIFFUSE,		bird.aura.c	)

					gl.Color( bird.aura.c )
					myogl.QUAD:Sphere( bird.aura.r, 10, 10 )

					--[[ local vertices = myogl.RetrieveVertices( "circle", bird.aura.r )
					myogl.RenderVertices( vertices, gl.POLYGON, bird.aura.w, bird.aura.c )
					]]

				end

				gl.Disable(gl.LINE_SMOOTH)
				if flocker.showRepel then
					local vertices = myogl.RetrieveVertices( "circle", flocker.repulseR )
					myogl.RenderVertices( vertices, gl.LINE_LOOP, 1, myogl.red )
				end

				if flocker.showDetect then
					local vertices = myogl.RetrieveVertices( "circle", flocker.detectR )
					myogl.RenderVertices( vertices, gl.LINE_LOOP, 1, myogl.green )
				end
				gl.Enable(gl.LINE_SMOOTH)

			gl.PopMatrix()
		end

	end

	gl.Disable( gl.CULL_FACE )
	for __, deader in pairs(displayPrimitives.deaders) do
		--{ p=vec3, d, r=int, w=int, c={} }
		gl.PushMatrix()

			local deaderpos = deader.p

			gl.Translate( deaderpos[1], deaderpos[2], deaderpos[3] )
			local vertices = myogl.RetrieveVertices( "circle", deader.r )
			myogl.RenderVertices( vertices,
				-- use polygons for 3D and circles for 2D; better visibility
				flocker.threeDeeFlocking and gl.POLYGON or gl.LINE_LOOP,
				deader.w, deader.c )

			--make sure the lines show up
			gl.Translate( 0, 0, -0.3 )
			gl.LineWidth( deader.w )
			gl.Begin( gl.LINES )
				gl.Color(  deader.line1.c )
				gl.Vertex( deader.line1.s )
				gl.Vertex( deader.line1.e )
			gl.End()
			gl.Begin( gl.LINES )
				gl.Color(  deader.line2.c )
				gl.Vertex( deader.line2.s )
				gl.Vertex( deader.line2.e )
			gl.End()

		gl.PopMatrix()
	end
	gl.Enable( gl.CULL_FACE )


	--[[These lines are at arbitrary positions/orientations so skip Push/Translate/Pop.
		Lasers use this primitive. :)
		]]
	for __, line in pairs( displayPrimitives.lines) do
		gl.LineWidth( line.w )
		gl.Begin( gl.LINES )
			gl.Color(  line.c )
			gl.Vertex( line.s )
			if line.ce then gl.Color( line.ce ) end
			gl.Vertex( line.e )
		gl.End()
	end


	for __, circle in pairs( displayPrimitives.circles) do
		gl.PushMatrix()

			-- circle == { p=vec3, r=int, w=int, c={} }
			local radius 	= math.floor(circle.r + 0.5)
			local vertices 	= myogl.RetrieveVertices( "circle", radius )
			local circlepos	= circle.p
			gl.Translate( circlepos[1], circlepos[2], circlepos[3] )
			myogl.RenderVertices( vertices, gl.LINE_LOOP, circle.w, circle.c )

		gl.PopMatrix()
	end

	-- no need to create a new vertex array for a singleton circle only drawn once per frame
	if flocker.trackCursor then
	-- need to fix how this works while threeDeeFlocking
		gl.Disable(gl.LINE_SMOOTH)

		gl. PushMatrix() -- just in case

			gl.Color( myogl.magenta )
			local circle =
				{
				  x = flocker.cursorPos[1]
				, y = flocker.cursorPos[2]
				, r = flocker.repulseR
				, w = 2
				, c = { 1,0,1,1 }
				}
			myogl.DrawCircle( circle, 100 )

		gl. PopMatrix()

		gl.Enable(gl.LINE_SMOOTH)

	end

	gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, myogl.black ) -- reset

	gl.Finish()

	-- time to show our work
	iup.GLSwapBuffers( self )


end



function flocker.ogl.TestFixture( self )

	-- syntax sweetener
	local myogl 	= flocker.ogl
	local lighting	= myogl.lighting
	local avatar 	= myogl.avatar
	local camera 	= myogl.camera

	-- swap to new buffer
	iup.GLMakeCurrent( self )

	-- clean up display
	local bgcolor = flocker.colorMap[ flocker.bgColor or 1 ]

	gl.ClearColor( bgcolor[1], bgcolor[2], bgcolor[3], bgcolor[4] )
	gl.Clear(gl.COLOR_BUFFER_BIT)
	gl.Clear(gl.DEPTH_BUFFER_BIT)

	-- for vertexpointer caching
	gl.EnableClientState(gl.VERTEX_ARRAY)

	-- enable smooth lines
	gl.Enable(gl.BLEND)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	gl.Enable(gl.LINE_SMOOTH)

	--[[ Turn on back-face culling -- this has no effect on frame rate under current conditions ]]
	gl.CullFace( gl.BACK )
	gl.Enable( gl.CULL_FACE )

	-- set the viewport
	gl.Viewport(0, 0, flocker.toroidX, flocker.toroidY)

	-- prepare to configure ortho/perspective
	-- DO SO BEFORE calls to glulookat
	gl.MatrixMode( gl.PROJECTION )
	gl.LoadIdentity()

	-- configure view based one 2D/3D
	if flocker.threeDeeFlocking then

		-- may want to come back and invert the z-axis usage all around
		glu.Perspective(camera.fov, flocker.toroidX / flocker.toroidY, 1, flocker.toroidZ*9 )

		local ax, ay, az = camera:GetVectorToAvatar()

		-- use the ModelView matrix for the rest of the rendering
		gl.MatrixMode( gl.MODELVIEW )
		gl.LoadIdentity()

		glu.LookAt(
				-- eye position relative to avatar
				avatar.x - ax,	avatar.y - ay,		avatar.z - az,
				-- we are always looking at the avatar
				avatar.x, 		avatar.y, 			avatar.z,
				-- we always have our head up
				0, 1, 0)

		gl.Enable(gl.DEPTH_TEST)

	else
		-- use world coordinates that match the cdCanvas ones
		gl.Ortho(flocker.toroidX, 0, 0, flocker.toroidY, -1, flocker.toroidZ)

		-- use the ModelView matrix for the rest of the rendering
		gl.MatrixMode( gl.MODELVIEW )
		gl.LoadIdentity()

		gl.Disable(gl.DEPTH_TEST)

	end

	-- always configure lighting
	lighting.ConfigureOpenGL()

	if flocker.threeDeeFlocking then

		-- use some shiner material than the default
		local worldBoxMaterial = flocker.ogl.CreateMaterialFromPrototype(
			{
				  ambient	= { 0.5, 0.5, 0.5, 1 }
				, diffuse	= { 0.5, 0.5, 0.5, 1 }
				, specular	= { 0.8, 0.8, 0.8, 1 }
				, shininess	= 10
				, emission	= { 0, 0, 0, 1 }
			}
		)

		-- so we can use spotlights on world box
		worldBoxMaterial:ConfigureMaterialOpenGL()

		-- so that threeD stuff is overlapped correctly
		myogl.DrawWorldBox(true, 0 ) -- (solid, gap at edges)

		--[[ HMM - when I put this block after all the other rendering (where I
			can use transparency) it interferes with the lighting. I need to
			figure out why that is, but it is a result of calling
					myogl.QUAD:(Cylinder,etc.)
		]]
		-- display the avatar
		gl.Disable( gl.CULL_FACE ) -- we can move around it so no culling
		gl.PushMatrix()
			gl.Translate( avatar.x, avatar.y, avatar.z  )
			gl.Rotate( avatar.yaw,		0,1,0 )
			gl.Rotate( avatar.pitch,	1,0,0 )

			gl.Color( {0,0,0, 1} )
			myogl.QUAD:DrawStyle( glu.LINE )
			gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	{1, 1, 0, 1}	)
			myogl.QUAD:Cylinder( 3, 7, 10, 20, 3)

			local height = 5
			gl.Translate( 0 ,0, -height )

			gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	{0, 1, 0, 1}	)
			myogl.QUAD:Cylinder( 3, 3, height, 20, 2)

		gl.PopMatrix()
		gl.Enable( gl.CULL_FACE )

	end


	for __, light in pairs(lighting.lights) do
		-- show the lights

		gl.Color( light.diffuse )
		if light.enabled then

			gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	light.diffuse	)

			gl.PushMatrix()

				gl.Translate( light.worldPosition[1], light.worldPosition[2], light.worldPosition[3]  )

				if light.spotEnabled then
					gl.Rotate( light.spotPhi,	0,0,1 )
					gl.Rotate( light.spotTheta,	0,1,0 )

					myogl.QUAD:DrawStyle( glu.LINE )
					myogl.QUAD:Cylinder( 5, 30, 60, 10, 10)

					gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, 	myogl.black	)
					myogl.QUAD:DrawStyle( glu.FILL )
				--	myogl.QUAD:DrawStyle( glu.LINE )
					myogl.QUAD:Sphere(10, 10, 10 )

				else
				--	myogl.QUAD:DrawStyle( glu.FILL )
					myogl.QUAD:DrawStyle( glu.LINE )
					myogl.QUAD:Sphere(20, 15, 15 )

				end
			gl.PopMatrix()

		end

	end

	gl.Material( gl.FRONT_AND_BACK, gl.EMISSION, myogl.black ) -- reset



if true then
	local x,y,z = avatar:GetDirectionVector( 10 )
	local pitch, yaw, length = OGLCartesianToPitchAndYawOGL( x, y, z, true )
	gl.LineWidth(10)
	gl.Color(  myogl.blue )
	gl.Color(  myogl.blue )
--print("xyz->pyl", x,y,z, pitch,yaw,length)

	gl.PushMatrix()
		gl.Translate( avatar.x, avatar.y, avatar.z  )
		gl.Rotate( yaw,		0,1,0 )
		gl.Rotate( pitch,	1,0,0 )

		gl.Begin(gl.LINES)
			gl.Vertex( {0,0,0} )
			gl.Vertex( {0,0,10} )
		gl.End()
	gl.PopMatrix()
end

if false then
--[[
		gl.LineWidth(10)
		gl.Color(  myogl.magenta )
		gl.Color(  myogl.magenta )

		local x,y,z = 0,0,10
		local pitch, yaw, length = OGLCartesianToPitchAndYawOGL( x,y,z, true )
		gl.PushMatrix()
			gl.Translate( avatar.x, avatar.y, avatar.z  )
			gl.Rotate( pitch,	1,0,0 )
			gl.Rotate( yaw,		0,1,0 )

			gl.Begin(gl.LINES)
				gl.Vertex( {0,0,0} )
				gl.Vertex( {0,0,length} )
			gl.End()
		gl.PopMatrix()

		gl.Color(  myogl.blue )
		local x,y,z = 0,10,0
		local pitch, yaw, length = OGLCartesianToPitchAndYawOGL( x,y,z, true )
		gl.PushMatrix()
			gl.Translate( avatar.x, avatar.y, avatar.z  )
			gl.Rotate( pitch,	1,0,0 )
			gl.Rotate( yaw,		0,1,0 )

			gl.Begin(gl.LINES)
				gl.Vertex( {0,0,0} )
				gl.Vertex( {0,0,length} )
			gl.End()
		gl.PopMatrix()

		gl.Color(  myogl.green )
		local x,y,z = 10,0,0
		local pitch, yaw, length = OGLCartesianToPitchAndYawOGL( x,y,z, true )
--print("xyz->pyl", x,y,z, pitch,yaw,length)
		gl.PushMatrix()
			gl.Translate( avatar.x, avatar.y, avatar.z  )
			gl.Rotate( pitch,	1,0,0 )
			gl.Rotate( yaw,		0,1,0 )

			gl.Begin(gl.LINES)
				gl.Vertex( {0,0,0} )
				gl.Vertex( {0,0,length} )
			gl.End()
		gl.PopMatrix()
]]
end



--[[ work in progress :) ]]
	if flocker.enableFog then
		gl.Fog(gl.FOG_COLOR, {0.5, 0.5, 0.5, 0.5} )

		gl.Fog(gl.FOG_DENSITY, flocker.ogl.fogIntensity)

		gl.Fog(gl.FOG_START, flocker.toroidZ/2)
		gl.Fog(gl.FOG_END, flocker.toroidZ)

	--	gl.Hint (gl.FOG_HINT, gl.NICEST);

		-- options include LINEAR, EXP, and EXP2
		gl.Fog(gl.FOG_MODE, gl.EXP2)

		gl.Enable(gl.FOG)

	else
		gl.Disable(gl.FOG)

	end

	gl.Disable(gl.DEPTH_TEST)


	local cx,cy = flocker.toroidX/2, flocker.toroidY/2

	gl.Color( 1, 1, 1, 0.5)

	local SS = 40
	gl.Begin(gl.POLYGON)

		gl.Vertex(cx-SS,	cy-SS, 		-SS)
		gl.Vertex(cx+SS,	cy-SS, 		-SS)
		gl.Vertex(cx+SS,	cy+SS, 		-SS)
		gl.Vertex(cx-SS,	cy+SS,  	-SS)
	gl.End()


	myogl.DrawCircle( { x=cx, y=cy, r=40, w=10, c={ 0.7, 0.0, 0.0 } 	} )

	-- line width
	gl.LineWidth( 3 )

	-- circle == { x, y, r, w, c={r,g,b} }

	local vertices  = myogl.CreateCircle( 200, 40 )



	gl.PushMatrix()
		gl.Translate( cx, cy, -2 )
		vertices:RenderVertices( gl.POLYGON, 2, {1, .75, .25, 1} )
	gl.PopMatrix()


	myogl.DrawCircle( { x=cx, y=cy, r=240, w=2, c={ 0.7, 0.0, 0.0 } }, 40 )
	myogl.DrawCircle( { x=cx, y=cy, r=180, w=3, c={ 0.0, 0.7, 0.0 } }, 30 )
	myogl.DrawCircle( { x=cx, y=cy, r=120, w=3, c={ 0.0, 0.0, 0.7 } }, 20 )
	myogl.DrawCircle( { x=cx, y=cy, r=60,  w=3, c={ 0.7, 0.7, 0.0 }	}, 10 )
	myogl.DrawCircle( { x=cx, y=cy, r=30,  w=3, c={ 0.0, 0.7, 0.7 }	}, 10 )

	self.counter = math.mod( self.counter+1, 15 )
	myogl.DrawCircle( { x=cx, y=cy, r=self.counter,  w=1, c={ 0.7, 0.0, 0.7 } }, 5*self.counter )

	-- draw box around it
	gl.Begin( gl.LINE_LOOP )

		gl.Color( 1,0,1 )
		gl.Vertex( 2, 2)
		gl.Vertex( 2, flocker.toroidY-2)
		gl.Vertex(flocker.toroidX-2,flocker.toroidY-2)
		gl.Vertex(flocker.toroidX-2, 2)

	gl.End()

	-- clock hands
	gl.LineWidth( 4 )
	local OO = 3000
	local II = -3000
	gl.Begin( gl.LINES )

		--gl.Vertex(cx, cy)
		local x,y = math.cos( self.angle + self.p1 ),  math.sin( self.angle + self.p1 )
		gl.Color(1.0, 0.5, 0.0, 0)
		gl.Vertex( cx + II*x, cy + II*y, 1 )
		gl.Color(1.0, 0.5, 0.0, 1)
		gl.Vertex( cx + OO*x, cy + OO*y, 1 )

		local x,y = math.cos( self.angle + self.p2 ),  math.sin( self.angle + self.p2 )
		--gl.Vertex(cx, cy)
		gl.Color(1.0, 0.0, 0.0, 0)
		gl.Vertex( cx + II*x, cy + II*y, 1 )
		gl.Color(1.0, 0.0, 0.0, 1)
		gl.Vertex( cx + OO*x, cy + OO*y, 1 )

	gl.End()

	iup.GLSwapBuffers( self )

end
